﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  EtwTraceController.cs
//    
// Abstract:
//
//  This module implements the EtwTraceController class.
//  
//--

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.OfficeCommunicationsServer.Applications.Common.UnitTests
{
    /// <summary>
    /// Implementation of a simple ETW controller for the benefit of the LoggerTest unit tests.
    /// </summary>
    internal sealed class EtwTraceController
    {
        // Handle to the ETW session
        private ulong _handle;

        // Name of the ETW session
        private string _sessionName;

        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="sessionName">Name of the ETW session</param>
        internal EtwTraceController(string sessionName)
        {
            Debug.Assert(!String.IsNullOrEmpty(sessionName), "!String.IsNullOrEmpty(sessionName)");

            _sessionName = sessionName;
        }

        /// <summary>
        /// Start the ETW session.
        /// </summary>
        internal void StartTrace()
        {
            Debug.Assert(_handle == 0, "_handle == 0");

            NativeMethods.EVENT_TRACE_PROPERTIES properties = CommonEvenTraceProperties();
            properties.WNode.Flags = NativeMethods.WNodeFlags.TracedGuid;
            properties.WNode.ClientContext = 1; //QPC clock resolution
            properties.LogFileMode = NativeMethods.EventTraceFileMode.RealTime;

            uint processResult = NativeMethods.StartTrace(out _handle, _sessionName, ref properties);

            int lastError = Marshal.GetLastWin32Error();

            if (!NativeMethods.IsValidHandle(_handle))
            {
                throw new Win32Exception(lastError);
            }

            if (processResult != NativeMethods.ERROR_SUCCESS && processResult != NativeMethods.ERROR_CANCELLED)
            {
                throw new Win32Exception((int)processResult);
            }
        }

        /// <summary>
        /// Stop the ETW session (if one exists).
        /// </summary>
        internal void StopTrace()
        {
            if (_handle != 0 && NativeMethods.IsValidHandle(_handle))
            {
                StopTrace(_handle, null);
            }
        }

        /// <summary>
        /// Stop the named ETW session (if it exists).
        /// </summary>
        /// <param name="sessionName">Name of the ETW session</param>
        /// <remarks>
        /// This method is called by the unit tests prior to creating an instance of this controller to ensure that 
        /// the specified ETW session doesn't already exist.
        /// </remarks>
        internal static void StopTrace(string sessionName)
        {
            Debug.Assert(!String.IsNullOrEmpty(sessionName), "!String.IsNullOrEmpty(sessionName)");

            StopTrace(0, sessionName);
        }

        /// <summary>
        /// Enable/Disable the ETW session.
        /// </summary>
        /// <param name="enable">Indicates if the session should be enabled or disabled</param>
        internal void EnableTrace(bool enable)
        {
            Debug.Assert(_handle != 0 && NativeMethods.IsValidHandle(_handle), "_handle != 0 && NativeMethods.IsValidHandle(_handle)");

            NativeMethods.InternalGuid providerGuid = new NativeMethods.InternalGuid(EtwTraceProvider_Accessor.Guid.ToByteArray());

            uint processResult = NativeMethods.EnableTrace(enable ? 1U : 0U, 0 /* enableFlag */, 5 /* enableLevel */, ref providerGuid, _handle);

            if (processResult != NativeMethods.ERROR_SUCCESS && processResult != NativeMethods.ERROR_CANCELLED)
            {
                throw new Win32Exception((int)processResult);
            }
        }

        /// <summary>
        /// Flush the ETW session.
        /// </summary>
        internal void FlushTrace()
        {
            Debug.Assert(0 != _handle && NativeMethods.IsValidHandle(_handle), "0 != _handle && NativeMethods.IsValidHandle(_handle)");

            NativeMethods.EVENT_TRACE_PROPERTIES properties = CommonEvenTraceProperties();

            uint processResult = NativeMethods.FlushTrace(_handle, _sessionName, ref properties);

            if (processResult != NativeMethods.ERROR_SUCCESS && processResult != NativeMethods.ERROR_CANCELLED)
            {
                throw new Win32Exception((int)processResult);
            }
        }

        /// <summary>
        /// Stop the named ETW session (if it exists).
        /// </summary>
        /// <param name="handle">Handle to the ETW session</param>
        /// <param name="sessionName">Name of the ETW session</param>
        /// <remarks>
        /// The session should be specified by either the handle or the session name.
        /// </remarks>
        private static void StopTrace(ulong handle, string sessionName)
        {
            Debug.Assert(!String.IsNullOrEmpty(sessionName) || handle != 0, "!String.IsNullOrEmpty(sessionName) || handle != 0");

            NativeMethods.EVENT_TRACE_PROPERTIES properties = CommonEvenTraceProperties();

            uint processResult = NativeMethods.StopTrace(handle, sessionName, ref properties);

            if (processResult != NativeMethods.ERROR_SUCCESS && 
                processResult != NativeMethods.ERROR_CANCELLED && 
                processResult != NativeMethods.ERROR_WMI_INSTANCE_NOT_FOUND) // It is expected that the ETW session might not exist
            {
                throw new Win32Exception((int)processResult);
            }
        }

        /// <summary>
        /// Initializes some of the fields of the NativeMethods.EVENT_TRACE_PROPERTIES structure with values that are common to
        /// all the ETW methods used in this class.
        /// </summary>
        /// <returns>
        /// The initialized NativeMethods.EVENT_TRACE_PROPERTIES structure.
        /// </returns>
        private static NativeMethods.EVENT_TRACE_PROPERTIES CommonEvenTraceProperties()
        {
            NativeMethods.EVENT_TRACE_PROPERTIES properties = new NativeMethods.EVENT_TRACE_PROPERTIES();
            properties.WNode.BufferSize = NativeMethods.EventTracePropertiesStructSize + NativeMethods.EventTracePropertiesStringSize;
            properties.WNode.Guid = new NativeMethods.InternalGuid(EtwTraceProvider_Accessor.Guid.ToByteArray());
            properties.LoggerNameOffset = NativeMethods.EventTracePropertiesStructSize;
            properties.LogFileNameOffset = 0;
            return properties;
        }
    }
}
